Skip to content

merge: integrate clusterfuck branch (40 commits)#97

Merged
maxholman merged 42 commits intomainfrom
merge/clusterfuck
Mar 20, 2026
Merged

merge: integrate clusterfuck branch (40 commits)#97
maxholman merged 42 commits intomainfrom
merge/clusterfuck

Conversation

@maxholman
Copy link
Copy Markdown
Contributor

Summary

Integrates the clusterfuck branch that was pushed but never PR'd/merged. 40 commits, 16 merge conflicts resolved.

  • Relay peer announcement (topology visibility through relays)
  • Heartbeat-based disconnect detection
  • Capabilities stored at registration (not two-step)
  • Per-instance IPC socket paths
  • Shadowed clones, REASON/INVARIANT comments codebase sweep
  • Proto noun_verb naming convention alignment
  • hint command eliminated — unified into role (role entry/prefer/exclude/auto)
  • OpenAPI operationId consistency

Conflicts resolved by keeping main's newer infrastructure (logs feature, neli 0.7, PskFailTracker removal, client→source rename, deduped QUIC/WS client/server) while integrating clusterfuck's refactors.

How to verify

  • just check passes
  • Range: verify relay topology visibility, role commands, heartbeat disconnect detection

🤖 Generated with Claude Code

maxholman and others added 30 commits March 17, 2026 21:06
…reaker

Auto-negotiation between two TUN-capable nodes currently deadlocks at
Indeterminate. The new `interactive` field in `Capabilities` lets a
node signal "human at terminal", which the negotiation logic (next commit)
uses as a deterministic tiebreaker.

All existing Capabilities struct literals default to `interactive: false`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aker

When both peers are TUN-capable, the interactive flag (human at terminal)
breaks the deadlock deterministically. Adds reason field to Resolved variant
for traceability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-mode detects whether stdin is a terminal and advertises it in the
handshake. Explicit modes always set interactive=false since their role
is predetermined.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…address

Previously all accepted relay peers were registered with their socket address as name and NodeRole::Relay hardcoded. Now extracts the peer's configured name and derives role from advertised capabilities, matching how entry/exit modes register peers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Source and accepted peers had no heartbeat — no latency tracking, no liveness checks, and disconnect_peer IPC couldn't reach them. Spawns heartbeat for both connection paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ent daemons

Multiple daemon instances on the same host (e.g. entry + relay) previously
fought over a single wallhackd.sock. The daemon now derives a socket filename
from the mode name (wallhackd-entry.sock, wallhackd-relay.sock, etc.).
The WALLHACK_HOST env var and -H flag still override for explicit addressing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
node_state.update_capabilities in auto mode now reflects the actual
interactive flag instead of hardcoding false — status API no longer
misreports what was advertised in the handshake.

REPL path uses the mode-derived socket name, matching the headless path
so concurrent REPL-attached daemons no longer collide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing `disconnect` command kills the entire transport session.
`disconnect-peer <name>` surgically drops a single peer connection,
which is the intended operation when managing multi-peer topologies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MCP agents can now steer auto-negotiation without CLI access via
set_hint (prefer/exclude/fixed) and clear_hints tools. The status→info
rename aligns MCP with the CLI naming convention: info = node identity,
stats = traffic counters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Headless orchestration (CI, monitoring, MCP fallback) previously required
IPC for connect/listen/disconnect/ping/shutdown/hints. All operations
are now available over HTTPS with the same auth model. The /status→/info
rename aligns with CLI and MCP naming: info = identity, stats = traffic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace "upstream" in DisconnectCmd doc with neutral terminology.
Rename single-char match bindings (c, l, p) to descriptive names
(connect, listen, ping) in new API handlers per naming standard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A TUN-capable node connecting to the listen side of a relay incorrectly
resolved to Entry — the negotiate() function couldn't distinguish which
side of the relay chain the connector was on since the relay's handshake
was identical in both directions.

The relay now sends Fixed(Entry) in its accept-side handshake, declaring
"I represent the entry side." negotiate() respects peer FIXED hints
before capability rules, so the accepted peer takes the complement and
resolves to Exit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All three interfaces now use the same pattern: disconnect with an
optional peer argument. Omitting the peer disconnects the transport;
providing one disconnects that specific peer. Removes the separate
disconnect-peer subcommand/tool that broke parity with the REPL.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
route_add/route_remove and hint_set/hint_clear now mirror the REPL's
"route add" / "route remove" and "hint set" / "hint clear" verb order
instead of the inverted add_route/set_hint form.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
"auto" communicates intent — return to capability-based negotiation —
rather than the mechanism (clearing a data structure). REPL keeps
"clear" as a hidden alias for muscle memory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ntion

Pre-1.0 rename to establish noun_verb ordering (RouteAddRequest,
HintSetRequest, PeerDisconnectRequest) matching the CLI/REPL/MCP
command structure. StatusRequest/Response renamed to Info to match
the endpoint rename. ClearHints renamed to HintAuto.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…roto and all consumers

iproute2-style `del` instead of `remove`. HintSetAuto sits in the
HintSet family — clearing hints is setting the negotiation mode to auto.
No aliases — one name per operation across REPL, CLI, MCP, and REST.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…m terminal

build_local_handshake now takes a Capabilities struct instead of
positional bools — call sites are self-documenting. All modes
(entry/exit/relay/auto) derive interactive from stdin terminal
detection instead of hardcoding false.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ed tasks

Deduplicates handshake→name/role extraction into resolve_peer().
Spawned task clones use block-scoped shadowing instead of _cleanup
suffixes per naming standard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
routeAdd/routeDel/hintSet/hintSetAuto match the proto and interface
naming established in the wire rename.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
StatusResponse→InfoResponse, RouteAddRequest→RouteAddBody,
SetHintRequest→HintSetBody. Web UI generates TypeScript types from
these schema names, so they need to be consistent with the interface
naming convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All Arc::clone bindings now shadow the original name in block scope
instead of using _suffix or single-char names. Opaque abbreviations
(pa, ns, ru, r, m, p, e, cc, lhs) replaced with descriptive names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ility

Relays can now announce accepted peers to their source peer via a new
PeerAnnouncement message on the control stream. The entry sees peers
behind the relay without direct transport connectivity.

Control loop forwards announcements through a new peer_announcement_tx
channel — wiring in the daemon modes is the next commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The relay subscribes to its own peer registry events and forwards
PeerAnnouncement messages over the existing control stream to the source
peer. The server-side control loop registers announced peers directly in
the entry's registry — no new channels or transport needed.

Entry nodes now see peers behind relays in their peers list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ived role

The relay's accept-side handshake forces all accepted peers to Exit via
Fixed(Entry) hint. The peer registry and announcement must reflect the
negotiated role, not the raw capability guess (which incorrectly shows
TUN-capable peers as Entry).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The send-instructions task never exited because the fan-out channel
stayed alive — peers were never unregistered. Move cleanup to the
data-in task which exits when the peer's transport dies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
maxholman and others added 12 commits March 18, 2026 17:59
Move peer unregistration from the dead send-instructions path to the
data-in task which actually exits when the transport dies. Rename
"Peer departed" log to "Peer disconnected (via relay)" for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
"connected with" / "disconnected from" instead of "Peer announced" /
"Peer disconnected" — the receiving node didn't initiate the connection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…g 404

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Empty HOME/USER/XDG_RUNTIME_DIR (common in minimal VMs) produced
paths like /.wallhack/ instead of falling through to /tmp/wallhack-shared/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto mode registered peers with default (all-false) capabilities. Now extracts capabilities from the peer's handshake and updates the registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The heartbeat loop exits when send_ping fails (control channel closed). This is the most reliable connection-death signal — unregister the peer here instead of relying on transport stream tasks that may not exit promptly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…step default+update

Previously callers would register with default capabilities then immediately
call update_capabilities to set the real values from the handshake. Consolidate
into a single atomic operation by requiring capabilities at register() time.
Remove the now-dead update_capabilities method from Registry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iant

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hint command was protocol jargon. All role operations now go through
a single `role` command: `role entry` (hard set), `role prefer entry`
(soft), `role exclude entry`, `role auto` (clear). Also renames
ping_peer → peer_ping for noun_verb consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rfuck

# Conflicts:
#	crates/api/src/handlers.rs
#	crates/api/src/lib.rs
#	crates/cli/src/bin/wallhack.rs
#	crates/cli/src/cli.rs
#	crates/cli/src/repl.rs
#	crates/core/src/client/quic/mod.rs
#	crates/core/src/client/ws/mod.rs
#	crates/core/src/ipc.rs
#	crates/core/src/node_api.rs
#	crates/core/src/server/quic/mod.rs
#	crates/core/src/server/ws/mod.rs
#	crates/core/src/transport/protocol.rs
#	crates/daemon/src/mode/auto.rs
#	crates/daemon/src/mode/entry.rs
#	crates/daemon/src/mode/exit.rs
#	crates/daemon/src/mode/relay.rs
#	crates/mcp/src/tools.rs
#	crates/wire/proto/management.proto
#	website/src/data/openapi.json
The clusterfuck branch added /ping and /ping/{peer} REST endpoints.
Ping was removed in v0.12.0 — drop the handlers, routes, and
PingResponseBody struct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@maxholman maxholman merged commit 8c49af9 into main Mar 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant